package org.rowan.adi.camera;

import org.rowan.adi.Vector;

/**
 * Quaternions are used to represent 3-dimensional rotation. This allows smooth
 * appending and alteration of rotations while avoiding the gimbal lock. A quaternion
 * is defined by a vector component and a scalar (w) component. This can also be 
 * thought of as four scalar (x, y, z, w) components.
 * <p>
 * Rotational calculations using quaternions is simplified when the quaternion
 * is normalized to a unit quaternion. As this class of Quaternion objects is 
 * intended to be used for rotation only, all created Quaternion objects are 
 * automatically normalized and remain immutable.
 * 
 * @author Spence DiNicolantonio
 * @version 1.0
 */
public class Quaternion {
	
	/*********************************************
	 * MARK: Constants
	 *********************************************/
	
	/* The identity quaternion. */
	public static final Quaternion IDENTITY = new Quaternion(0, 0, 0, 1);
	
	
	/*********************************************
	 * MARK: Fields
	 *********************************************/
	
	/* The vector component of this Quaternion. */
	private double x, y, z;
	
	/* The scalar component of this Quaternion. */
	private double w;
	
	
	/*********************************************
	 * MARK: Constructors
	 *********************************************/	
		
	/**
	 * Designated Constructor.
	 * <p>
	 * Creates a Quaternion with given x, y, z, and w components. The created 
	 * Quaternion will be normalized automatically.
	 * @param x	The x component to be set in the created Quaternion.
	 * @param y	The y component to be set in the created Quaternion.
	 * @param z The z component to be set in the created Quaternion.
	 * @param w	The w component to be set in the created Quaternion.
	 */
	public Quaternion(double x, double y, double z, double w) {
		// set component values
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		
		// normalize zero value components
		normalizeZeros();
		
		// normalize
		normalize();
	}
	
	/**
	 * Creates a Quaternion with given given vector and scalar components. The 
	 * created Quaternion will be normalized automatically.
	 * @param vector	The vector component to be set in the created Quaternion.
	 * @param scalar	The scalar component to be set in the created Quaternion.
	 */
	public Quaternion(Vector vector, double scalar) {
		// call designated constructor constructor
		this(vector.getX(), vector.getY(), vector.getZ(), scalar);
	}
	
	
	/*********************************************
	 * MARK: Accessors
	 *********************************************/	
	
	/**
	 * Returns the x component of this Quaternion.
	 * @return The x component of this Quaternion.
	 */
	public double getX() {
		return this.x;
	}
	
	/**
	 * Returns the y component of this Quaternion.
	 * @return The y component of this Quaternion.
	 */
	public double getY() {
		return this.y;
	}
	
	/**
	 * Returns the z component of this Quaternion.
	 * @return The z component of this Quaternion.
	 */
	public double getZ() {
		return this.z;
	}
	
	/**
	 * Returns the w component of this Quaternion.
	 * @return The w component of this Quaternion.
	 */
	public double getW() {
		return this.w;
	}
	
	/**
	 * Returns the scalar component of this Quaternion. This is the same as the 
	 * w component.
	 * @return The scalar component of this Quaternion.
	 */
	public double scalarComponent() {
		return this.w;
	}
	
	/**
	 * Returns the vector component of this Quaternion. This is a Vector object
	 * created using the x, y, and z components of this Quaternion.
	 * @return The vector component of this Quaternion.
	 */
	public Vector vectorComponent() {
		return new Vector(this.x, this.y, this.z);
	}
	
	
	/*********************************************
	 * MARK: Queries
	 *********************************************/	
	
	/**
	 * Calculates the magnitude of this Quaternion.
	 * @return	The magnitude of this Quaternion.
	 */
	public double magnitude() {
		// calculate magnitude using the Pythagorian theorem
		return Math.sqrt(x*x + y*y + z*z + w*w);
	}
	
	/**
	 * Determines whether this Quaternion is the identity quaternion. The identity 
	 * quaternion is a unit quaternion with scalar component equal to 1.
	 * @return	<code>true</code> if this Quaternion is the identity quaternion;
	 *			<code>false</code> otherwise.
	 */
	public boolean isIdentity() {
		return (this.x == 0.0 &&
				this.y == 0.0 &&
				this.z == 0.0 &&
				this.w == 1.0);
	}
	
	/**
	 * Determines whether this Quaternion is a zero quaternion. A zero quaternion 
	 * is a quaternion with 0 magnitude (all component values equal 0). The rotation
	 * represented by a zero quaternion is undefined.
	 * @return	<code>true</code> if this Quaternion is a zero quaternion; 
	 * 			<code>false</code> otherwise.
	 */
	public boolean isZeroQuaternion() {
		return (magnitude() == 0);
	}
	
	/**
	 * Determines whether this Quaternion is a unit quaternion. A unit quaternion 
	 * is a quaternion with magnitude of 1.0.
	 * @return	<code>true</code> if this Quaternion is a unit quaternion; 
	 *			<code>false</code> otherwise.
	 */
	public boolean isUnitQuaternion() {
		return (magnitude() == 1);
	}
	
	
	/*********************************************
	 * MARK: Mutators
	 *********************************************/	
	
	/**
	 * Normalizes this Quaternion to a unit quaternion with the same direction. 
	 * If this Quaternion is a zero quaternion, this method has no effect.
	 */
	public void normalize() {
		// zero quaternion?
		if (isZeroQuaternion())
			return;
		
		// normalize to magnitude of 1.0
		double magnitude = magnitude();
		this.x /= magnitude;
		this.y /= magnitude;
		this.z /= magnitude;
		this.w /= magnitude;
	}
	
	
	/*********************************************
	 * MARK: Arithmetic
	 *********************************************/	
	
	/**
	 * Creates a unit quaternion with the same direction as this Quaternion. If 
	 * this Quaternion is a zero quaternion, a zero quaternion is returned.
	 * @return	A unit quaternion with the same direction as this Quaternion. A 
	 *			zero quaternion if this Quaternion is a zero quaternion.
	 */
	public Quaternion unitQuaternion() {
		// copy quaternion
		Quaternion unitQuaternion = new Quaternion(this.x, this.y, this.z, this.w);
		
		// normalize and return copy
		unitQuaternion.normalize();
		return unitQuaternion;
	}
	
	/**
	 * Calculates the quaternion inverse of this Quaternion.
	 * @return	A Quaternion with the same magnitude as this Quaternion, but 
	 *			opposite direction.
	 */
	public Quaternion inverse() {
		// create a new Quaternion with an inverse vector component
		return new Quaternion(-this.x, -this.y, -this.z, this.w);
	}
	
	/**
	  * Multiplies this Quaternion by a given Quaternion. 
	  * @param quaternion	A Quaternion object to be multiplied by this Quaternion.
	  * @return				The product of this Quaternion and the given Quaternion.
	  */
	public Quaternion multiply(Quaternion quaternion) {
		// calculate component values
		double x = (this.w * quaternion.getX()) + (this.x * quaternion.getW()) + (this.y * quaternion.getZ()) - (this.z * quaternion.getY());
		double y = (this.w * quaternion.getY()) - (this.x * quaternion.getZ()) + (this.y * quaternion.getW()) + (this.z * quaternion.getX());
		double z = (this.w * quaternion.getZ()) + (this.x * quaternion.getY()) - (this.y * quaternion.getX()) + (this.z * quaternion.getW());
		double w = (this.w * quaternion.getW()) - (this.x * quaternion.getX()) - (this.y * quaternion.getY()) - (this.z * quaternion.getZ());
		
		// create and return Quaternion product using calculated component values
		return new Quaternion(x, y, z, w);
	}
	
	
	/*********************************************
	 * MARK: toString
	 *********************************************/	
	
	/**
	 * Creates a string to describe this Quaternion.
	 * @return	A string that describes this Quaternion.
	 */
	public String toString() {
		return ("("+this.x+", "+this.y+", "+this.z+", "+this.w+")");
	}
	
	
	/*********************************************
	 * MARK: Private
	 *********************************************/	
	
	/**
	 * Normalizes -0.0 value components in this Quaternion to 0.0. This method is used
	 * by the Quaternion constructor to maintain aesthetics.
	 */
	private void normalizeZeros() {
		// if value is -0.0 set it to 0.0
		this.x = (this.x == 0.0) ? 0.0 : this.x;
		this.y = (this.y == 0.0) ? 0.0 : this.y;
		this.z = (this.z == 0.0) ? 0.0 : this.z;
		this.w = (this.w == 0.0) ? 0.0 : this.w;
	}
}

